package com.shaoyu.tools.event.core;


import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.reflect.TypeToken;
import com.shaoyu.tools.event.annotation.EventInterceptor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * @Desc :
 * @Author: shaoyu
 * @Date : 2022/11/2 13:43
 */
public class InterceptorRegister {

    ExecutorService executor;

    private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<SubscribeInterceptor>> subscribeInterceptors = Maps.newConcurrentMap();

    public InterceptorRegister(ExecutorService executor) {
        this.executor = executor;
    }

    public void registerInterceptor(Object listener) {
        Multimap<Class<?>, SubscribeInterceptor> listenerMethods = this.findAllInterceptor(listener);
        Set<Map.Entry<Class<?>, Collection<SubscribeInterceptor>>> entries = listenerMethods.asMap().entrySet();
        for (Map.Entry<Class<?>, Collection<SubscribeInterceptor>> entry : entries) {
            Class<?> eventType = entry.getKey();
            Collection<SubscribeInterceptor> eventInterceptorMethodsInListener = entry.getValue();
            CopyOnWriteArraySet<SubscribeInterceptor> subscribeInterceptors = this.subscribeInterceptors.get(eventType);
            if (subscribeInterceptors == null) {
                subscribeInterceptors = new CopyOnWriteArraySet<>();
                this.subscribeInterceptors.putIfAbsent(eventType, subscribeInterceptors);
            }
            subscribeInterceptors.addAll(eventInterceptorMethodsInListener);
        }
    }

    private Multimap<Class<?>, SubscribeInterceptor> findAllInterceptor(Object listener) {
        Multimap<Class<?>, SubscribeInterceptor> methodsInListener = HashMultimap.create();
        Class<?> clazz = listener.getClass();
        Iterator<Method> iterator = getInterceptorMethods(clazz).iterator();
        while (iterator.hasNext()) {
            Method method = (Method) iterator.next();
            Class<?>[] parameterTypes = method.getParameterTypes();
            Class<?> eventType = parameterTypes[0];
            methodsInListener.put(eventType, SubscribeInterceptor.create(this.executor, listener, method));
        }
        return methodsInListener;
    }

    private CopyOnWriteArraySet<SubscribeInterceptor> getInterceptors(Class<?> aClass) {
        return this.subscribeInterceptors.getOrDefault(aClass,new CopyOnWriteArraySet<>());
    }

    private List<Method> getInterceptorMethods(Class<?> clazz) {
        Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
        List<Method> interceptorMethods = new ArrayList<>();
        Iterator<? extends Class<?>> iterator = supertypes.iterator();
        while (iterator.hasNext()) {
            Class<?> supertype = (Class) iterator.next();
            Method[] methods = supertype.getDeclaredMethods();
            int length = methods.length;
            for (int i = 0; i < length; ++i) {
                Method method = methods[i];
                if (method.isAnnotationPresent(EventInterceptor.class) && !method.isSynthetic()) {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    Class<?> returnType = method.getReturnType();
                    Preconditions.checkArgument(parameterTypes.length == 1, "Method %s has @EventInterceptor annotation but has %s parameters. EventInterceptor methods must have exactly 1 parameter.", new Object[]{method, parameterTypes.length});
                    Preconditions.checkArgument(Boolean.class.equals(returnType) || boolean.class.equals(returnType), "Method %s has @EventInterceptor annotation but return type is %s .EventInterceptor methods must return Boolean.", new Object[]{method, returnType.getSimpleName()});
                    interceptorMethods.add(method);
                }
            }
        }
        return interceptorMethods;
    }

    public void unregisterInterceptor(Object listener) {
        Multimap<Class<?>, SubscribeInterceptor> listenerMethods = this.findAllInterceptor(listener);
        Set<Map.Entry<Class<?>, Collection<SubscribeInterceptor>>> entries = listenerMethods.asMap().entrySet();
        for (Map.Entry<Class<?>, Collection<SubscribeInterceptor>> entry : entries) {
            Class<?> eventType = entry.getKey();
            Collection<SubscribeInterceptor> eventInterceptorMethodsInListener = entry.getValue();
            CopyOnWriteArraySet<SubscribeInterceptor> subscribeInterceptors = this.subscribeInterceptors.get(eventType);
            if (subscribeInterceptors != null) {
                subscribeInterceptors.remove(eventInterceptorMethodsInListener);
            }
        }
    }

    public boolean intercepted(Object event) {
        CopyOnWriteArraySet<SubscribeInterceptor> subscribeInterceptors = this.getInterceptors(event.getClass());
        List<Future<Boolean>> interceptedResult = new ArrayList<>();
        subscribeInterceptors.forEach(interceptor -> interceptedResult.add(interceptor.dispatchEvent(event)));
        for (Future<Boolean> booleanFuture : interceptedResult) {
            try {
                if (!booleanFuture.get()) {
                    return true;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}
